Moduł:Timeline
Module: Timeline -- Author: User:DuckeyD -- Inspired by: EasyTimeline by Erik Zachte -- Version: 1.1 -- local Timeline = {} -- Dependencies local colors = require('Dev:Colors') -- Constants local YEAR_LENGTH = 60 * 60 * 24 * 365.2 local MONTH_LENGTH = 60 * 60 * 24 * 30.44 -- Template function function Timeline.main(frame) -- Extract arguments. local parentFrame = frame:getParent() local templateArgs = {} for p, v in pairs(parentFrame.args) do templateArgsp = v end -- Extract function name as first argument. local fn = templateArgs1 and table.remove(templateArgs, 1) -- Check for function argument. if fn nil then error((mw.ustring.gsub(mw.ustring.match(mw.message.new('scribunto-common-nofunction'):plain(), ':%s(.*)%p$'), '^.', mw.ustring.lower))) end -- Check function exists. if Timelinefn nil then error((mw.ustring.gsub(mw.ustring.match(mw.message.new('scribunto-common-nosuchfunction'):plain(), ':%s(.*)%p$'), '^.', mw.ustring.lower))) end -- Execute function if it does. parentFrame.args = templateArgs return Timelinefn(parentFrame) end -- Main invokable function function Timeline.create(frame) -- Load config from a given module local conf = mw.loadData('Module:'..assert(frame.args1, 'Config module not passed as a frame argument')) local container = mw.html.create('div') -- Style parameters local background_color = colors.parse('$color-text'):invert() local text_color = colors.params'color-text' local timeline_padding = 4 local labels_width = 120 local bar_height = 16 local bar_margin = 8 local chart_margin = 10 local chart_major = colors.params'color-text' local chart_minor = colors.parse('$color-text'):alpha(50):hex() local bar_background = background_color local bar_alpha = 40 local legend_columns = 3 -- Visibility parameters local timeline_hidden = false local legend_hidden = false local background_hidden = false -- Custom styling from config if conf.style then if conf.style.background_color then background_color = colors.parse(conf.style.background_color) end -- CSS Color if conf.style.text_color then text_color = conf.style.text_color end -- CSS Color if conf.style.timeline_padding then timeline_padding = conf.style.timeline_padding end -- number if conf.style.labels_width then labels_width = conf.style.labels_width end -- number if conf.style.bar_height then bar_height = conf.style.bar_height end -- number if conf.style.bar_margin then bar_margin = conf.style.bar_margin end -- number if conf.style.chart_margin then chart_margin = conf.style.chart_margin end -- number if conf.style.chart_major then chart_major = conf.style.chart_major end -- CSS Color if conf.style.chart_minor then chart_minor = conf.style.chart_minor end -- CSS Color if conf.style.bar_background then bar_background = conf.style.bar_background end -- CSS Color if conf.style.bar_alpha then bar_alpha = conf.style.bar_alpha end -- number if conf.style.legend_columns then legend_columns = conf.style.legend_columns end -- number -- conf.style.label_format -- string end -- Custom visibility settings from config if conf.hidden then if conf.hidden.timeline then timeline_hidden = conf.hidden.timeline end if conf.hidden.legend then legend_hidden = conf.hidden.legend end if conf.hidden.background then background_hidden = conf.hidden.background end end local chart_width = 700 - (labels_width + timeline_padding*2 + chart_margin) -- Root element styling container:css({ 'box-sizing' = 'border-box', 'display' = 'flex', 'width' = '700px', 'padding' = timeline_padding..'px', 'background-color' = background_color:hex(), 'color' = text_color, 'flex-wrap' = 'wrap', 'margin' = (timeline_padding*3)..'px 0' }) -- Separator for Mercury container:node(mercuryOnly(mw.html.create('hr'))) -- Labels local labels = mw.html.create('div') labels:css({ 'width' = labels_width..'px', 'text-align' = 'right', 'line-height' = bar_height..'px', 'font-size' = (0.75 * bar_height)..'px' }) for _, label in pairs(assert(conf.dataset, 'No dataset found in config')) do local label_elem = mw.html.create('div'):css({ 'height' = bar_height..'px', 'margin' = bar_margin..'px 0' }) local oasis_elem = oasisOnly(mw.html.create('span')) local mercury_elem = mercuryOnly(mw.html.create('span')) -- Custom label formatting - add style.label_format string containing "$name" to config if conf.style and conf.style.label_format then oasis_elem:wikitext((string.gsub(conf.style.label_format, '$name', (assert(label.name, 'No "name" on label '.._))))) mercury_elem:wikitext(' \'\'\''..string.gsub(conf.style.label_format, '$name', label.name)..'\'\'\'') else oasis_elem:wikitext((assert(label.name, 'No "name" on label '.._))) mercury_elem:wikitext(' \'\'\''..label.name..'\'\'\'') end label_elem:node(oasis_elem) label_elem:node(mercury_elem) -- Main Mercury design local mercury_list = mercuryOnly(mw.html.create('ul')) for _, bar in ipairs(assert(label.bars, 'No "bars" on label '..label.name)) do if assert(assert(conf.bar_types, 'No bar_types defined in config')'No "bar_type" key on a bar in label '..label.name), 'Bar type '..bar.bar_type..' not found').legend then local mercury_from = os.date('%d/%m/%Y', dateToTimestamp(assert(bar.from, 'No "from" key on a bar in label '..label.name), conf)) local mercury_till = os.date('%d/%m/%Y', dateToTimestamp(assert(bar.till, 'No "till" key on a bar in label '..label.name), conf)) local mercury_label = conf.bar_typesbar.bar_type.legend mercury_list:node(mw.html.create('li'):wikitext(mercury_label..': '..mercury_from..' – '..mercury_till)) end end label_elem:node(mercury_list) labels:node(label_elem) end container:node(labels) -- Chart local chart = oasisOnly(mw.html.create('div')) chart:css({ 'width' = chart_width..'px', 'margin-left' = (chart_margin-1)..'px', 'border-left' = '1px solid '..chart_major, 'border-bottom' = '1px solid '..chart_major }) if not background_hidden then local chart_bg, chart_offset = generateBackground( assert(conf.from, 'No "from" key found in config'), assert(conf.till, 'No "till" key found in config'), chart_width, chart_major, chart_minor ) chart:css({ 'background-image' = chart_bg, 'background-position-x' = chart_offset }) end local chart_from = dateToTimestamp(conf.from) local chart_till = dateToTimestamp(conf.till) local chart_diff = chart_till - chart_from bar_background:alpha(bar_alpha) -- Chart bars for _, label in pairs(conf.dataset) do local bar_container = mw.html.create('div'):css({ 'height' = bar_height..'px', 'margin' = bar_margin..'px 0', 'background-color' = bar_background:rgb(), 'position' = 'relative' }) for _, bar in ipairs(label.bars) do local bar_from = dateToTimestamp(bar.from, conf) local bar_till = dateToTimestamp(bar.till, conf) bar_container:node(mw.html.create('div'):css({ 'height' = bar_height..'px', 'background' = assert(conf.bar_typesbar.bar_type.color, 'No color on bar type '..bar.bar_type), 'position' = 'absolute', 'left' = (((bar_from - chart_from)/chart_diff)*chart_width)..'px', 'right' = (((chart_till - bar_till)/chart_diff)*chart_width)..'px' })) end chart:node(bar_container) end container:node(chart) -- Timeline if not timeline_hidden then local chart_timeline = mw.html.create('div'):css({ 'width' = chart_width..'px', 'height' = (0.75 * bar_height)..'px', 'line-height' = (0.75 * bar_height)..'px', 'margin-left' = (labels_width + chart_margin)..'px', 'font-size' = (0.625 * bar_height)..'px', 'position' = 'relative' }) for i = math.floor(chart_from/YEAR_LENGTH) + 1, math.floor(chart_till/YEAR_LENGTH), 1 do chart_timeline:node(mw.html.create('div'):wikitext(1970+i):css({ 'position' = 'absolute', 'left' = (((dateToTimestamp('01/01/'..(1970+i)) - chart_from)/chart_diff)*chart_width)..'px', 'transform' = 'translate(-50%, 0)' })) end container:node(oasisOnly(chart_timeline)) end -- Legend if not legend_hidden then local legend = mw.html.create('div'):css({ 'margin-top' = bar_height..'px', 'margin-left' = (labels_width + chart_margin)..'px', 'font-size' = (0.75*bar_height)..'px', 'width' = chart_width..'px', 'display' = 'flex', 'flex-wrap' = 'wrap' }) for _, bar_type in pairs(conf.bar_types) do if bar_type.legend then local label_elem = mw.html.create('div'):css({ 'display' = 'flex', 'align-items' = 'center', 'height' = bar_height..'px', 'width' = (100/legend_columns)..'%' }) label_elem:node(mw.html.create('div'):css({ 'width' = (0.75*bar_height)..'px', 'height' = (0.75*bar_height)..'px', 'background' = bar_type.color, 'margin-right' = (bar_margin/2)..'px' })) if bar_type.order then label_elem:css('order', bar_type.order) end label_elem:wikitext(bar_type.legend) legend:node(label_elem) end end container:node(oasisOnly(legend)) end -- Separator for Mercury container:node(mercuryOnly(mw.html.create('br'))) container:node(mercuryOnly(mw.html.create('hr'))) return container end -- Helper functions function generateBackground(from, till, width, year_color, month_color) local start_date = dateToTimestamp(from) local end_date = dateToTimestamp(till) local diff = end_date - start_date local background = 'repeating-linear-gradient(to right, transparent' local month_multiplier = (MONTH_LENGTH*width)/diff for i = 1, 11, 1 do background = background..generateBar(i * month_multiplier, month_color) end background = background..generateBar(12 * month_multiplier, year_color)..')' local offset = (start_date % YEAR_LENGTH)*width/diff offset = '-'..offset..'px' return background, offset end function generateBar(pos, bar) pos = math.floor(pos) return ', transparent '..pos..'px, '..bar..' '..pos..'px, '..bar..' '..(pos+1)..'px, transparent '..(pos+1)..'px' end function mercuryOnly(elem) return elem:css('display', 'none !important') end function oasisOnly(elem) return elem:addClass('mobile-hidden') end function dateToTimestamp(d, conf) if d 'now' then return os.time() end if d 'start' and conf.from then return dateToTimestamp(conf.from) end if d 'end' and conf.till then return dateToTimestamp(conf.till) end return os.time({ year = d:sub(7, 10), month = d:sub(4, 5), day = d:sub(1, 2), hour = 0, min = 0, sec = 0 }) end return Timeline --